/*
 *  Copyright (C) 2010-2013 JPEXS
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.jpexs.decompiler.flash.abc.avm2.parser;

import com.jpexs.decompiler.flash.abc.avm2.AVM2Code;
import com.jpexs.decompiler.flash.abc.avm2.ConstantPool;
import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction;
import com.jpexs.decompiler.flash.abc.avm2.instructions.InstructionDefinition;
import com.jpexs.decompiler.flash.abc.types.ABCException;
import com.jpexs.decompiler.flash.abc.types.MethodBody;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class ASM3Parser {

   private static class OffsetItem {

      public String label = "";
      public long insPosition;
      public int insOperandIndex;

      public OffsetItem(String label, long insOffset, int insOperandIndex) {
         this.label = label;
         this.insPosition = insOffset;
         this.insOperandIndex = insOperandIndex;
      }
   }

   private static class CaseOffsetItem extends OffsetItem {

      public CaseOffsetItem(String label, long insOffset, int insOperandIndex) {
         super(label, insOffset, insOperandIndex);
      }
   }

   private static class LabelItem {

      public String label = "";
      public int offset;

      public LabelItem(String label, int offset) {
         this.label = label;
         this.offset = offset;
      }
   }

   public static AVM2Code parse(InputStream is, ConstantPool constants, MethodBody body) throws IOException, ParseException {
      return parse(is, constants, null, body);
   }

   public static AVM2Code parse(InputStream is, ConstantPool constants, MissingSymbolHandler missingHandler, MethodBody body) throws IOException, ParseException {
      AVM2Code code = new AVM2Code();

      List<OffsetItem> offsetItems = new ArrayList<OffsetItem>();
      List<LabelItem> labelItems = new ArrayList<LabelItem>();
      List<ABCException> exceptions = new ArrayList<ABCException>();
      List<Integer> exceptionIndices = new ArrayList<Integer>();
      int offset = 0;

      Flasm3Lexer lexer = new Flasm3Lexer(is);

      ParsedSymbol symb;
      AVM2Instruction lastIns = null;
      do {
         symb = lexer.yylex();
         if (symb.type == ParsedSymbol.TYPE_EXCEPTION_START) {
            int exIndex = (Integer) symb.value;
            int listIndex = exceptionIndices.indexOf(exIndex);
            if (listIndex == -1) {
               throw new ParseException("Undefinex exception index", lexer.yyline());
            }
            exceptions.get(listIndex).start = offset;
            continue;
         }
         if (symb.type == ParsedSymbol.TYPE_EXCEPTION_END) {
            int exIndex = (Integer) symb.value;
            int listIndex = exceptionIndices.indexOf(exIndex);
            if (listIndex == -1) {
               throw new ParseException("Undefinex exception index", lexer.yyline());
            }
            exceptions.get(listIndex).end = offset;
            continue;
         }
         if (symb.type == ParsedSymbol.TYPE_EXCEPTION_TARGET) {
            int exIndex = (Integer) symb.value;
            int listIndex = exceptionIndices.indexOf(exIndex);
            if (listIndex == -1) {
               throw new ParseException("Undefinex exception index", lexer.yyline());
            }
            exceptions.get(listIndex).target = offset;
            continue;
         }
         if (symb.type == ParsedSymbol.TYPE_EOF) {
            break;
         }
         if (symb.type == ParsedSymbol.TYPE_COMMENT) {
            if (lastIns != null) {
               lastIns.comment = (String) symb.value;
            }
            continue;
         }
         if (symb.type == ParsedSymbol.TYPE_INSTRUCTION_NAME) {
            if (((String) symb.value).toLowerCase().equals("exception")) {
               ParsedSymbol exIndex = lexer.yylex();
               if (exIndex.type != ParsedSymbol.TYPE_INTEGER) {
                  throw new ParseException("Index expected", lexer.yyline());
               }
               ParsedSymbol exName = lexer.yylex();
               if (exName.type != ParsedSymbol.TYPE_MULTINAME) {
                  throw new ParseException("Multiname expected", lexer.yyline());
               }
               ParsedSymbol exType = lexer.yylex();
               if (exType.type != ParsedSymbol.TYPE_MULTINAME) {
                  throw new ParseException("Multiname expected", lexer.yyline());
               }
               ABCException ex = new ABCException();
               ex.name_index = (int) (long) (Long) exName.value;
               ex.type_index = (int) (long) (Long) exType.value;
               exceptions.add(ex);
               exceptionIndices.add((int) (long) (Long) exIndex.value);
               continue;
            }
            boolean insFound = false;
            for (InstructionDefinition def : AVM2Code.instructionSet) {
               if (def.instructionName.equals((String) symb.value)) {
                  insFound = true;
                  List<Integer> operandsList = new ArrayList<Integer>();

                  for (int i = 0; i < def.operands.length; i++) {
                     ParsedSymbol parsedOperand = lexer.yylex();
                     switch (def.operands[i]) {
                        case AVM2Code.DAT_MULTINAME_INDEX:
                           if (parsedOperand.type == ParsedSymbol.TYPE_MULTINAME) {
                              operandsList.add((int) (long) (Long) parsedOperand.value);
                           } else {
                              throw new ParseException("Multiname expected", lexer.yyline());
                           }
                           break;
                        case AVM2Code.DAT_STRING_INDEX:
                           if (parsedOperand.type == ParsedSymbol.TYPE_STRING) {
                              int sid = constants.getStringId((String) parsedOperand.value);
                              if (sid == 0) {
                                 if ((missingHandler != null) && (missingHandler.missingString((String) parsedOperand.value))) {
                                    sid = constants.addString((String) parsedOperand.value);
                                 } else {
                                    throw new ParseException("Unknown String", lexer.yyline());
                                 }
                              }
                              operandsList.add(sid);
                           } else {
                              throw new ParseException("String expected", lexer.yyline());
                           }
                           break;
                        case AVM2Code.DAT_INT_INDEX:

                           if (parsedOperand.type == ParsedSymbol.TYPE_INTEGER) {
                              long intVal = (Long) parsedOperand.value;
                              int iid = constants.getIntId(intVal);
                              if (iid == 0) {
                                 if ((missingHandler != null) && (missingHandler.missingInt(intVal))) {
                                    iid = constants.addInt(intVal);
                                 } else {
                                    throw new ParseException("Unknown int", lexer.yyline());
                                 }
                              }
                              operandsList.add(iid);
                           } else {
                              throw new ParseException("Integer expected", lexer.yyline());
                           }
                           break;
                        case AVM2Code.DAT_UINT_INDEX:
                           if (parsedOperand.type == ParsedSymbol.TYPE_INTEGER) {
                              long intVal = (Long) parsedOperand.value;
                              int iid = constants.getUIntId(intVal);
                              if (iid == 0) {
                                 if ((missingHandler != null) && (missingHandler.missingUInt(intVal))) {
                                    iid = constants.addUInt(intVal);
                                 } else {
                                    throw new ParseException("Unknown uint", lexer.yyline());
                                 }
                              }
                              operandsList.add(iid);
                           } else {
                              throw new ParseException("Integer expected", lexer.yyline());
                           }
                           break;
                        case AVM2Code.DAT_DOUBLE_INDEX:
                           if ((parsedOperand.type == ParsedSymbol.TYPE_INTEGER) || (parsedOperand.type == ParsedSymbol.TYPE_FLOAT)) {

                              double doubleVal = 0;
                              if (parsedOperand.type == ParsedSymbol.TYPE_INTEGER) {
                                 doubleVal = (Long) parsedOperand.value;
                              }
                              if (parsedOperand.type == ParsedSymbol.TYPE_FLOAT) {
                                 doubleVal = (Double) parsedOperand.value;
                              }
                              int did = constants.getDoubleId(doubleVal);
                              if (did == 0) {
                                 if ((missingHandler != null) && (missingHandler.missingDouble(doubleVal))) {
                                    did = constants.addDouble(doubleVal);
                                 } else {
                                    throw new ParseException("Unknown double", lexer.yyline());
                                 }
                              }
                              operandsList.add(did);
                           } else {
                              throw new ParseException("Float value expected", lexer.yyline());
                           }
                           break;
                        case AVM2Code.DAT_OFFSET:
                           if (parsedOperand.type == ParsedSymbol.TYPE_IDENTIFIER) {
                              offsetItems.add(new OffsetItem((String) parsedOperand.value, code.code.size(), i));
                              operandsList.add(0);
                           } else {
                              throw new ParseException("Offset expected", lexer.yyline());
                           }
                           break;
                        case AVM2Code.DAT_CASE_BASEOFFSET:
                           if (parsedOperand.type == ParsedSymbol.TYPE_IDENTIFIER) {
                              offsetItems.add(new CaseOffsetItem((String) parsedOperand.value, code.code.size(), i));
                              operandsList.add(0);
                           } else {
                              throw new ParseException("Offset expected", lexer.yyline());
                           }
                           break;
                        case AVM2Code.OPT_CASE_OFFSETS:

                           if (parsedOperand.type == ParsedSymbol.TYPE_INTEGER) {
                              int patCount = (int) (long) (Long) parsedOperand.value;
                              operandsList.add(patCount);

                              for (int c = 0; c <= patCount; c++) {
                                 parsedOperand = lexer.yylex();
                                 if (parsedOperand.type == ParsedSymbol.TYPE_IDENTIFIER) {
                                    offsetItems.add(new CaseOffsetItem((String) parsedOperand.value, code.code.size(), i + (c + 1)));
                                    operandsList.add(0);
                                 } else {
                                    throw new ParseException("Offset expected", lexer.yyline());
                                 }
                              }
                           } else {
                              throw new ParseException("Case count expected", lexer.yyline());
                           }
                           break;
                        default:
                           if (parsedOperand.type == ParsedSymbol.TYPE_INTEGER) {
                              operandsList.add((int) (long) (Long) parsedOperand.value);
                           } else {
                              throw new ParseException("Integer expected", lexer.yyline());
                           }
                     }
                  }

                  int operands[] = new int[operandsList.size()];
                  for (int i = 0; i < operandsList.size(); i++) {
                     operands[i] = operandsList.get(i);
                  }
                  lastIns = new AVM2Instruction(offset, def, operands, new byte[0]);
                  code.code.add(lastIns);
                  offset += lastIns.getBytes().length;
                  break;
               }
            }
            if (!insFound) {
               throw new ParseException("Invalid instruction name:" + (String) symb.value, lexer.yyline());
            }
         } else if (symb.type == ParsedSymbol.TYPE_LABEL) {
            labelItems.add(new LabelItem((String) symb.value, offset));

         } else {
            throw new ParseException("Unexpected symbol", lexer.yyline());
         }
      } while (symb.type != ParsedSymbol.TYPE_EOF);

      code.compact();

      for (OffsetItem oi : offsetItems) {
         for (LabelItem li : labelItems) {
            if (oi.label.equals(li.label)) {
               AVM2Instruction ins = code.code.get((int) oi.insPosition);
               int relOffset;
               if (oi instanceof CaseOffsetItem) {
                  relOffset = li.offset - (int) ins.offset;
               } else {
                  relOffset = li.offset - ((int) ins.offset + ins.getBytes().length);
               }
               ins.operands[oi.insOperandIndex] = relOffset;
            }
         }
      }
      body.exceptions = new ABCException[exceptions.size()];
      for (int e = 0; e < exceptions.size(); e++) {
         body.exceptions[e] = exceptions.get(e);
      }
      return code;
   }
}
